#include "Morph.h"

#define pweight 0.5
#define bweight	0.5
#define aweight 0.01
#define background 255

//Morph algorythm constructor

void Morph::initialitze(ImagenRGB ImagenA, ImagenRGB ImagenB,
		Segments &ASegments, Segments& BSegments) {
	ImASegments = ASegments;
	ImBSegments = BSegments;
	ImA = ImagenA;
	ImB = ImagenB;

}

//Write the num_images iteration in a file
void Morph::generateMorph(int num_images, char * OutImageFilename) {

	//Generate "num_images" morphed images.
	for (int i = 1; i <= num_images; i++) {
		generateImage(i, num_images, OutImageFilename);
	}
}

//Calculate the image in the num_images iteration
void Morph::generateImage(int step, int num_images, char * OutImageFilename) {

	ImagenRGB resultImage = ImA;

	Point x_prima_finalA;
	Point x_prima_finalB;

	//Calcular los segmentos correspondientes a la imagen numero "step"
	Segments tmp_segments = intermediateSegments(step, num_images);

	double imgARedVal, imgAGreenVal, imgABlueVal;
	double imgBRedVal, imgBGreenVal, imgBBlueVal;
	int colA, filA, colB, filB;
	bool posAout, posBout;
	//Por cada pixel en la imagen destino:

	for (int fil = 0; fil < ImA.dim[0]; fil++) {
		for (int col = 0; col < ImA.dim[1]; col++) {

			//Obtener el punto final de donde leeremos el color en A.
			x_prima_finalA = getXPrimaFinal(Point(col, fil), tmp_segments,
					ImASegments);
			colA = redondear(x_prima_finalA.x);
			filA = redondear(x_prima_finalA.y);

			//Obtener el punto final de donde leeremos el color en B.
			x_prima_finalB = getXPrimaFinal(Point(col, fil), tmp_segments,
					ImBSegments);

			colB = redondear(x_prima_finalB.x);
			filB = redondear(x_prima_finalB.y);

			 posAout = false;
			 posBout = false;

			//Asignamos los colores segun la interpolacion.
			if (filA >= ImA.dim[0] || filA < 0 || colA >= ImA.dim[1] || colA
					< 0) {
				//Cae fuera de A.
				posAout = true;
			}
			if (filB >= ImB.dim[0] || filB < 0 || colB >= ImB.dim[1] || colB
								< 0) {
				//Cae fuera de B
				posBout = true;
			}

			if(posAout == false)
			{
				//Si A cae dentro asignar valores interpolados de A.
				imgARedVal = ImA.RChannel.datos[(ImA.dim[1]) * filA + colA]
						* (num_images + 1 - step) / ((double) num_images + 1);
				imgAGreenVal = ImA.GChannel.datos[(ImA.dim[1]) * filA + colA]
						* (num_images + 1 - step) / ((double) num_images + 1);
				imgABlueVal = ImA.BChannel.datos[(ImA.dim[1]) * filA + colA]
						* (num_images + 1 - step) / ((double) num_images + 1);
			}
			if (posBout == false) {
				//Si B cae dentro asignar valores interpolados de B.
				imgBRedVal = ImB.RChannel.datos[(ImB.dim[1]) * filB + colB]
						* (step) / ((double) num_images + 1);
				imgBGreenVal = ImB.GChannel.datos[(ImB.dim[1]) * filB + colB]
						* (step) / ((double) num_images + 1);
				imgBBlueVal = ImB.BChannel.datos[(ImB.dim[1]) * filB + colB]
						* (step) / ((double) num_images + 1);
			}
			//minimizar la distorsion si uno o los dos caen fuera:
			if((posAout == true) && (posBout == false))
			{

				imgARedVal = ImB.RChannel.datos[(ImA.dim[1]) * filB + colB]
				        						* (num_images + 1 - step) / ((double) num_images + 1);
				imgAGreenVal = ImB.GChannel.datos[(ImA.dim[1]) * filB + colB]
				          						* (num_images + 1 - step) / ((double) num_images + 1);
				imgABlueVal = ImB.BChannel.datos[(ImA.dim[1]) * filB + colB]
				         						* (num_images + 1 - step) / ((double) num_images + 1);
			}
			else if ((posAout == false) && (posBout == true))
			{
				imgBRedVal = ImA.RChannel.datos[(ImB.dim[1]) * filA + colA]
				        						* (step) / ((double) num_images + 1);
				imgBGreenVal = ImA.GChannel.datos[(ImB.dim[1]) * filA + colA]
				          						* (step) / ((double) num_images + 1);
				imgBBlueVal = ImA.BChannel.datos[(ImB.dim[1]) * filA + colA]
				         						* (step) / ((double) num_images + 1);
			}
			else if ((posAout == true) && (posBout == true))
			{
				imgARedVal = background;
				imgAGreenVal = background;
				imgABlueVal = background;

				imgBRedVal = 0;
				imgBGreenVal = 0;
				imgBBlueVal = 0;
			}

			double proba = imgARedVal
			+ imgBRedVal;
			proba = imgABlueVal
						+ imgBBlueVal;
			proba = imgAGreenVal
						+ imgBGreenVal;

			//assignar colors a la imatge resultant, tenint en compte les dues imatges
			resultImage.RChannel.datos[(ImA.dim[1]) * fil + col] = imgARedVal
					+ imgBRedVal;
			resultImage.GChannel.datos[(ImA.dim[1]) * fil + col] = imgAGreenVal
					+ imgBGreenVal;
			resultImage.BChannel.datos[(ImA.dim[1]) * fil + col] = imgABlueVal
					+ imgBBlueVal;
		}
	}

	char* filename = new char;
	sprintf(filename, "%s%d.tiff", OutImageFilename, step);
	resultImage.writeImageFile(filename);
}

//Calculate UV with the actual pixel and the segment which is being calculated
Point Morph::calculateUV(Point actual_point, Segment segmentDest) {

	//Calcular u
	Point pointTmp1 = actual_point - segmentDest.initial_point;
	Point pointTmp2 = segmentDest.final_point - segmentDest.initial_point;
	double u = pointTmp1 * pointTmp2;
	double mod = module(segmentDest.initial_point, segmentDest.final_point);

	u = u / (mod * mod);

	Point perpTemp1 = segmentDest.final_point - segmentDest.initial_point;
	Point perpTemp2 = Point(-(perpTemp1.y), perpTemp1.x);

	//Calcular v
	double tmpV = pointTmp1 * perpTemp2;

	double v = tmpV / mod;

	return Point(u, v);
}

//calculate X with u,v and the segments of the image
Point Morph::calculateX(Point pointUV, Segment segmentSrc) {

	Point perpTemp1 = segmentSrc.final_point - segmentSrc.initial_point;
	Point perpTemp2 = Point(-(perpTemp1.y), perpTemp1.x);
	Point tmp1 = (segmentSrc.final_point - segmentSrc.initial_point)
			* pointUV.x;
	Point tmp2 = (perpTemp2 * pointUV.y) / module(segmentSrc.initial_point,
			segmentSrc.final_point);
	Point x_prima = segmentSrc.initial_point + tmp1 + tmp2;

	return x_prima;
}

//cacula the minimum distance between the actual point and one segment
double Morph::calculateMinDistance(Point actual_point, Point pointUV,
		Segment segmentDest) {
	double distance = 0;
	double u, v;
	u = pointUV.x;
	v = pointUV.y;
	if (u > 1)
		distance = module(segmentDest.final_point, actual_point);
	else if (u < 0)
		distance = module(segmentDest.initial_point, actual_point);
	else
		distance = abs(v);

	return distance;
}

//calculate the weight of a segment.
double Morph::calculaWeight(Segment segment, double distance) {
	double weight, length;
	length = pow(module(segment.initial_point, segment.final_point), pweight);
	weight = pow((length / (aweight + distance)), bweight);
	return weight;
}

//calculate all the intermediate segments wich will be used for the "step" image.
Segments Morph::intermediateSegments(int step, int num_images) {

	Segments calculatedSegments;
	Segment tmpSegment;
	Segment tmpSegmentA;
	Segment tmpSegmentB;
	double dStep = step;
	double dNumImages = num_images;

	for (unsigned int i = 0; i < ImASegments.size(); i++) {
		//
		tmpSegmentA = ImASegments[i] * ((dNumImages + 1 - dStep) / (dNumImages
				+ 1));
		tmpSegmentB = ImBSegments[i] * ((dStep) / (dNumImages + 1));
		tmpSegment = tmpSegmentA + tmpSegmentB;
		calculatedSegments.push_back(tmpSegment);

	}
	return calculatedSegments;
}

//calculate the module of the vector formed by two points
double Morph::module(Point p, Point q) {
	Point tmp = q - p;
	double tmp1 = (tmp.x) * (tmp.x);
	double tmp2 = (tmp.y) * (tmp.y);
	return sqrt(tmp1 + tmp2);

}
//Calculate X' using the actual points and the destination and source image segments.
Point Morph::getXPrimaFinal(Point actual_point, Segments & tmp_segments,
		Segments & segmentsSrc) {
	Point DSum(0, 0);
	double weightsum;
	Point x_prima_i;

	Point Di;
	double distance;
	double weight;
	Point pointUV;
	Point DiWeight;
	weightsum = 0;

	//for each segment
	for (unsigned int num_segment = 0; num_segment < tmp_segments.size(); num_segment++) {
		pointUV = calculateUV(actual_point, tmp_segments[num_segment]);
		x_prima_i = calculateX(pointUV, segmentsSrc[num_segment]);
		Di = x_prima_i - actual_point;
		distance = calculateMinDistance(actual_point, pointUV,
				tmp_segments[num_segment]);
		weight = calculaWeight(tmp_segments[num_segment], distance);
		DiWeight = Di * weight;
		DSum += DiWeight;
		weightsum += weight;
	}
	DSum = DSum / weightsum;
	Point x_prima_final = actual_point + DSum;

	return x_prima_final;
}

